// // Copyright (c) 2009 All Right Reserved // // Stephen Toub // stoub@microsoft.com // 2009-01-01 // Contains Classes to represent MIDI events (voice, meta, system, etc). using System; using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Text; using LargoCommon.Music; namespace LargoCommon.Midi { /// A MIDI event, serving as the base class for all types of MIDI events. /// was abstract [Serializable] public class MidiEvent : ICloneable, IMidiEvent { #region Fields /// The amount of time before this event. private long deltaTime; #endregion #region Constructors /// Initializes a new instance of the MidiEvent class. /// The amount of time before this event. public MidiEvent(long deltaTime) { //// protected // Store the data this.DeltaTime = deltaTime; this.StartTime = deltaTime; } #endregion #region Properties /// /// Gets the type of the event. /// /// /// The type of the event. /// public string EventType { get { var eventType = this.GetType().ToString(); var dotPosition = eventType.LastIndexOf('.'); var pureType = eventType.Substring(dotPosition + 1); //// string dotPureType = Path.GetExtension(eventType); return pureType; } } /// /// Gets or sets the start time. /// /// /// The start time. /// public long StartTime { get; set; } /// Gets or sets the amount of time before this event. /// General musical property. public long DeltaTime { //// virtual (11/2010) get => this.deltaTime; set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), value, "Delta times must be non-negative."); } this.deltaTime = value; } } /// /// Gets or sets the bar number. /// /// /// The bar number. /// public int BarNumber { get; set; } /// /// Gets a value indicating whether IsVoiceNoteEvent. /// /// General musical property. public bool IsVoiceNoteEvent => this is VoiceAbstractNote voiceEvent && (voiceEvent.Channel != MidiChannel.DrumChannel); /// /// Gets a value indicating whether IsMetaEvent. /// /// General musical property. public bool IsMetaEvent { get { var eventType = this.EventType; switch (eventType) { case "MetaText": break; case "MetaCopyright": break; case "MetaSequenceTrackName": break; case "MetaInstrument": break; case "MetaLyric": break; case "MetaMarker": break; case "MetaCuePoint": break; case "MetaProgramName": break; case "MetaDeviceName": break; default: return false; } return true; } } #endregion #region Static methods /// Splits a 14-bit value into two bytes each with 7 of the bits. /// The value to be split. /// The upper 7 bits. /// The lower 7 bits. //// internal public static void Split14BitsToBytes(int bits, out byte upperBits, out byte lowerBits) { lowerBits = (byte)(bits & 0x7F); bits >>= 7; upperBits = (byte)(bits & 0x7F); } #endregion #region Public methods /// Write the event to the output stream. /// The stream to which the event should be written. public virtual void Write(Stream outputStream) { Contract.Requires(outputStream != null); //// Write out the delta time WriteVariableLength(outputStream, this.deltaTime); } #endregion #region To String /// Generate a string representation of the event. /// A string representation of the event. public override string ToString() { var sb = new StringBuilder(); sb.Append(this.GetType().Name.PadRight(12)); sb.Append("\t"); var startString = " StartTime =" + this.StartTime.ToString(CultureInfo.CurrentCulture.NumberFormat); sb.Append(startString.PadRight(12)); var deltaString = " DeltaTime =" + this.DeltaTime.ToString(CultureInfo.CurrentCulture.NumberFormat); sb.Append(deltaString.PadRight(12)); return sb.ToString(); } #endregion #region Public Implementation of ICloneable /// Creates a shallow copy of the MIDI event. /// A shallow-clone of the MIDI event. public IMidiEvent Clone() { return (MidiEvent)this.MemberwiseClone(); } #endregion #region Implementation of ICloneable /// Creates a shallow-copy of the object. /// A shallow-clone of the MIDI event. object ICloneable.Clone() { return this.Clone(); } #endregion #region Internal methods /// Combines two 7-bit values into a single 14-bit value. /// The upper 7-bits. /// The lower 7-bits. /// A 14-bit value stored in an integer. internal static int CombineBytesTo14Bits(byte upper, byte lower) { // Turn the two bytes into a 14 bit value int fourteenBits = upper; fourteenBits <<= 7; fourteenBits |= lower; return fourteenBits; } #endregion #region Protected methods /// Converts an array of bytes into human-readable form. /// The array to convert. /// The string containing the bytes. protected static string DataToString(byte[] givenData) { if (givenData == null) { return string.Empty; } var sb = new StringBuilder(); sb.Append("["); for (var i = 0; i < givenData.Length; i++) { //// If we're not the first byte, output a comma as a separator if (i > 0) { sb.Append(","); } //// Spit out the byte itself sb.Append("0x"); sb.Append(givenData[i].ToString("X2", CultureInfo.CurrentCulture.NumberFormat)); } sb.Append("]"); return sb.ToString(); } /// Writes bytes for a long value in the special 7-bit form. /// The stream to which the length should be written. /// The value to be converted and written. protected static void WriteVariableLength(Stream outputStream, long value) { Contract.Requires(outputStream != null); if (outputStream == null) { return; } // TODO: Clean this up! // Parse the value into bytes containing each set of 7-bits and a 1-bit marker // for whether there are more bytes in the length var buffer = value & 0x7f; while ((value >>= 7) > 0) { buffer <<= 8; buffer |= 0x80; buffer += value & 0x7f; } // Get all of the bytes in correct order while (true) { outputStream.WriteByte((byte)(buffer & 0xFF)); if ((buffer & 0x80) == 0) { break; } // if the marker bit is not set, we're done buffer >>= 8; } } #endregion } }